package nekomv.server;

import nekomv.server.ClientData;
import nekomv.server.TimerThread;
import general.Log;
import general.Component;

class MiniverseServer extends neko.net.ServerLoop<ClientData>
{

    public var time : Float;
    public var globalevents : List<String>;

    public var ball : general.Component;
    public var scene : general.Component;

    public function new()
    {
        scene = new Component(general.components.Scene.typeid, null);

        ball = new Component(general.components.SimpleMovable.typeid, null);
        ball.data = [scene.id,400,300,8,0];

        time = neko.Sys.time();
        globalevents = new List();
        super(clientconnect);
        TimerThread.mvserverinst = this;
        neko.vm.Thread.create(TimerThread.loop);
        Log.init("nekomv Server started");
    }

    public static function clientconnect(socket : neko.net.Socket) : ClientData
    {
        Log.init("Client connected");
        return new ClientData(socket);
    }
    
    public function onError( e : Dynamic )
    {
        Log.error(e);
    }

    public function clientDisconnected( d : ClientData)
    {
        Log.close("Client disconnected: "+d.peer+":"+d.port+
                 " - #"+Std.string(d.id));
    }

    public function update()
    {

        /// an important note about how update actually works.
        /// update is triggered when socket events are recieved and processed,
        /// or after a minimum time passes. (this time has a lower bound limited
        /// by neko's implementation of Socket)

        /// Thus it cannot be used by itself for accurate
        /// timing. What it can do, however, is form a stateless input backend
        /// while mutableupdate performs state changes.
        
        /// a consequence of this is that update() is empty, while TimerThread
        /// calls mutableupdate(), which does all the heavy lifting.
        
        /// it also means that two cores will be allocated such that one does
        /// socket inputs, and the other does processing and output.
        
    }

    public function mutableupdate()
    {
        for (c in clients)
        {
        
            for (p in c.parsequeue)
                parse(p,c);
            c.parsequeue.clear();
        }

        var ballx = ball.data[1];
        var bally = ball.data[2];
        var ballxs = ball.data[3];
        var ballys = ball.data[4];
        
        ballx = ballx+ballxs;
        if (ballx>800 || ballx<0) ballxs = -ballxs;
        bally = bally+ballys;
        if (bally>600) ballys = - 20 - Std.int(50 * Math.random());
        ballys += 3;
        
        ball.data[1] = ballx;
        ball.data[2] = bally;
        ball.data[3] = ballxs;
        ball.data[4] = ballys;

        var ballevent = "n"+general.NumberEncoding.encodeInt(ball.data[0])+
                        ":"+general.NumberEncoding.encodeInt(ball.data[1])+
                        ":"+general.NumberEncoding.encodeInt(ball.data[2])+
                        ":"+general.NumberEncoding.encodeInt(ball.data[3])+
                        ":"+general.NumberEncoding.encodeInt(ball.data[4])
                        ;

        globalevents.add(ballevent);

        for (c in clients)
        {
            for (e in globalevents)
            {
                var out = general.GenEvent.extended(e);
                clientWrite(c.socket, out, 0, out.length);    
            }

            for (e in c.events)
            {
                var out = general.GenEvent.extended(e);
                clientWrite(c.socket, out, 0, out.length);    
            }            

            c.events.clear();
            
        }
        globalevents.clear();
        
    }

    public function addGlobalEvent(e : String)
    {
        globalevents.add(e);
    }
    
    public function processClientData(client : ClientData, buf : String, 
                    bufpos : Int, buflen : Int) : Int
    {
        var inp = new neko.io.StringInput(buf);

        switch(inp.readChar())
        {

            /// there is a nasty abstraction leak here where we can't grab
            // the number of bytes snagged in any one read from an Input.
            // so we have to count it up by hand to tell the buffer how
            // much we want to remove.

            // fortunately any bug in the count becomes readily visible with
            // runtime testing.

            case(0):
                var extmsg = general.ReadEvent.extended(inp);
                parsequeue(extmsg, client);
                return extmsg.length+5;
            default: // I got bad data?
                bufpos++;
        }

        return bufpos;

    }

    public function parsequeue(input : String, client : ClientData)
    {
        client.parsequeue.add(input);
    }

    public inline function parse(input : String, client : ClientData)
    {
        var words = input.split(" ");
        switch(words[0])
        {
            default:
                ClientErrorEvent.pass("extended word "+words[0]+" is undefined", client);
            case("/say"):
                addGlobalEvent("e<"+client.nick+"> "+input.substr(5));
            case("/nick"):
                if (words.length==2)
                {
                    addGlobalEvent("e"+client.nick+" is now known as "+words[1]);
                    client.nick = words[1];
                }
                else
                    ClientErrorEvent.pass("/nick must be one un-space-separated word only", client);
        }
    }
    
}